/**
 * 根据json拼成react组件的ast结构，生成代码
 * 
 * json结构定义
 * export default {
    imports: [
        ['./wrapper', 'Wrapper']
    ],
    displayName: 'Pages',
    state: {
        visible: true,
    },
    props: {
        visible: "{{this.state.visible}}"
    },
    componentName: "Modal",
    children: [
        {
        props: {},
        componentName: "Wrapper",
        children: [
            {
            props: {
                isShow: "true",
                onClick: () => {
                setState({
                    visible: false,
                })
                },
            },
            componentName: "p",
            children: "这是一段{{this.props.content}}文章"
            },
        ],
        },
    ],
  }
 */

import _template from "@babel/template";
const template = _template.default;
import _generate from "@babel/generator";
const generate = _generate.default;
import { parse } from "@babel/parser";
import * as t from "@babel/types";
import fs from 'fs';
import serialize from 'serialize-javascript';

import prettier from "prettier";

const openTagRe = /{{/;
const closeTagRe = /}}/;

export default class JsonAstCode {
    constructor({
        entryPath,
        outputPath,
        projectConfig,
        finishedCallback,
    } = props) {

        // fs.writeFileSync('./test.ast.json', JSON.stringify(template(`export default function test(props) { return <div>{props.children}</div> }`, { plugins: ['jsx'] })(), null, 4));
        this.projectConfig = projectConfig;
        this.ast = [];
        const file = fs.readFileSync(entryPath, { encoding: 'utf-8' })
        const json = this.json = eval('[' + file.replace(/^export default /, '') + ']');
        //解析json
        const parseJson = (jsonData = [], ast = []) => {
            jsonData.forEach(node => {
                const jsx = this.getJSXElement(node);
                ast.push(jsx);
                if (Array.isArray(node.children) && node.children?.length > 0) {
                    parseJson(node.children, jsx.children);
                } else if (typeof node.children === 'string') {
                    jsx.children = [];
                    const childrenAst = this.getStringTemplateAst(node.children, true)
                    jsx.children = childrenAst;
                }
            });
        }
        parseJson(json, this.ast);
        // console.log('打印日志')
        // fs.writeFileSync('./test1.ast.json', JSON.stringify(this.ast, null, 4));
        // 拼接组件完整文件ast
        const fileAst = t.file(
            t.program(
                [
                    ...(this.getImport()),
                    this.getComponentContainer(
                        [
                            this.getStateAst(json[0].state)
                        ]
                            .filter(Boolean),
                        this.ast[0],
                    )
                ].filter(Boolean)
            )
        );
        
        // 生成代码({ jsescOption: {minimal:true} }生成中文)
        const result = generate(fileAst, { jsescOption: {minimal:true}  });
        // 代码格式化
        const formatCode = prettier.format(result.code);
        // 输出代码
        fs.writeFileSync(outputPath, formatCode);
        // 执行成功回调
        typeof finishedCallback === 'function' && finishedCallback();
    }

    /**
     * 拼接import
       支持全局配置autoImport 或  单个文件imports属性配置，格式一致
       示例：autoImport: [
        // 'react'做了特殊处理，可以简写'react'
        'react',              =>     import React from 'react';

        // 传入数组类型  
        ['from', 'import'],
        ['react', 'React']    =>   import React from 'react';

        //传入对象类型
        {
            [package-name]: [
                'import'
            ],
            'react': [
                'useState',
                'useEffect'
            ],   =>   import { useState, useEffect } from 'react';
            [package-name]: ['import', 'alias'],
            'react': [
                'useState',
                ['useLayoutEffect', 'asyncLayoutEffect']
            ],   =>   import { useState, useLayoutEffect as asyncLayoutEffect } from 'react';
        }
    ]
     */
    getImport = () => {
        const { autoImport } = this.projectConfig;
        const { imports = [] } = this.json[0];
        const allImport = new Set(autoImport.concat(imports));
        let importsStr = '';
        for (let imp of allImport) {
            if (Array.isArray(imp)) {
                if (typeof imp[0] === 'string' && typeof imp[1] === 'string') {
                    importsStr += `import ${imp[1]} from '${imp[0]}';`
                } else {
                    console.warn('请传入正确import格式');
                }
            } else if (typeof imp === 'object') {
                const [[key, value]] = Object.entries(imp);
                if (Array.isArray(value)) {
                    let importSet = new Set([]);
                    value.forEach(valueItem => {
                        if (Array.isArray(valueItem)) {
                            if (typeof valueItem[0] === 'string' && typeof valueItem[1] === 'string') {
                                importSet.add(`${valueItem[0]} as ${valueItem[1]}`);
                            }
                        } else if (typeof valueItem === 'string') {
                            importSet.add(`${valueItem}`);
                        }
                        
                    })
                    let importSetStr = '';
                    for (let item of importSet) {
                        importSetStr += ` ${item},`;
                    }
                    importSetStr = importSetStr.slice(0, -1);
                    importsStr += `import {${importSetStr} } from '${key}';`
                } else {
                    console.warn('请传入正确import格式');
                }
            } else if (typeof imp === 'string') {
                if (imp === 'react') {
                    importsStr += `import React from 'react';`
                } else {
                    importsStr += `import ${imp} from '${imp}';`
                }
            } else {
                console.warn('请传入正确import格式');
            }
        }
        if (importsStr.length <= 0) return [];
        const importAst = template(importsStr, { plugins: ['jsx'] })();
        return allImport.size > 1 ? importAst : [importAst];
    }

    getStateAst = (state) => {
        if (state && Object.keys(state).length > 0) {
            const stateStr = `const [state, setState] = React.useState(${serialize(state)})`
            return template(stateStr, { plugins: ['jsx'] })();
        }
        return false;
    }

    // 拼接函数组件容器
    getComponentContainer = (block = [], returnArgument) => {
        return {
            "type": "ExportDefaultDeclaration",
            "declaration": {
                "type": "FunctionDeclaration",
                "params": [
                    {
                        "type": "Identifier",
                        "name": "props"
                    }
                ],
                "generator": false,
                "async": false,
                "id": {
                    "type": "Identifier",
                    "name": this.json[0].displayName || "MyComponent"
                },
                "body": {
                    "type": "BlockStatement",
                    "directives": [],
                    "body": [
                        ...block,
                        {
                            "type": "ReturnStatement",
                            "argument": returnArgument
                        }
                    ]
                }
            }
        }
    }


    // // 拼接className, 支持直接import 和 CSS Module两种模式
    // getClassName = (className, ) => {
    //     const { styleImportType = 'import' } = this.projectConfig;

    //     let value = {
    //         "type": "StringLiteral",
    //         "value": className,
    //         "extra": {
    //             "rawValue": `${className}`,
    //             "raw": "'" + className + "'"
    //         }
    //     };

    //     if (styleImportType === 'css_module') {
    //         value = {
    //             "type": "JSXExpressionContainer",
    //             "expression": {
    //                 "type": "MemberExpression",
    //                 "object": {
    //                     "type": "Identifier",
    //                     "name": "styles"
    //                 },
    //                 "property": value,
    //                 "computed": true
    //             }
    //         }
    //     }

    //     return {
    //         "type": "JSXAttribute",
    //         "name": {
    //             "type": "JSXIdentifier",
    //             "name": "className"
    //         },
    //         "value": value,
    //     }
    // }

    // 拼接props
    getJSXAttribute = (props) => {
        if (!props) return [];
        let attr = [];
        for (let [key, value] of Object.entries(props)) {
            let valueAst;
            if (typeof value === 'function') {
                const valueExpressionAst = parse(value.toString());
                valueAst = t.jsxExpressionContainer(valueExpressionAst.program.body[0].expression);
            } else if (typeof value === 'string') {
                valueAst = this.getStringTemplateAst(value)[0];
            }

            let attrAst = {
                "type": "JSXAttribute",
                "name": {
                    "type": "JSXIdentifier",
                    "name": key
                },
                "value": valueAst
            }

            attr.push(attrAst);
        }
        return attr;
    }



    // 拼接JSX标签
    getJSXElement = (node) => {
        let children = [];
        const attrAst = this.getJSXAttribute(node.props);
        return {
            "type": "JSXElement",
            "openingElement": {
                "type": "JSXOpeningElement",
                "name": {
                    "type": "JSXIdentifier",
                    "name": node.componentName
                },
                "selfClosing": false,
                "attributes": attrAst
            },
            "closingElement": {
                "type": "JSXClosingElement",
                "name": {
                    "type": "JSXIdentifier",
                    "name": node.componentName
                }
            },
            "children": children
        }
    }


    /**
     * 解析字符串类型的children，包含模版语法解析
     * 示例：
     * "前{{this.state.visible}}后"  
     * =>
     * [
            {
                "type": "text",
                "value": "前"
            },
            {
                "type": "{",
                "value": "this.state.visible"
            },
            {
                "type": "text",
                "value": "后"
            }
        ]
     */
    parseStringTemplate = (stringTemplate) => {
        let tail = stringTemplate;
        // console.log('stringTemplate', stringTemplate)
        let tokens = [];
        let hasCloseTag = false;
        const scanStr = (tagRe) => {
            const index = tail.search(tagRe);
            let match = '';
            switch(index) {
                case -1: 
                    match = tail;
                    tail = '';
                    break;
                case 0: 
                    match = '';
                    break;
                default:
                    match = tail.substring(0, index);
                    tail = tail.substring(index);
            }
            return [match, index];
        }

        
        while(tail !== '') {
            // 匹配'{{'tag
            const [text, openIndex] = scanStr(openTagRe);
            
            if (text) {
                if (hasCloseTag) {
                    const textWithoutTag = text.replace(closeTagRe, '');
                    textWithoutTag && tokens.push({type: 'text', value: textWithoutTag })
                } else {
                    tokens.push({type: 'text', value: text })
                }
                if (openIndex === -1) {
                    continue;
                }
            }
            // 匹配'}}'tag
            const [value, closeIndex] = scanStr(closeTagRe);

            if (value) {
                hasCloseTag = true;
                if (closeIndex === -1) {
                    tokens.push({type: 'text', value })
                } else {
                    const valueWithoutTag = value.replace(openTagRe, '');
                    valueWithoutTag && tokens.push({type: '{', value: valueWithoutTag })
                }
            }
        }
        // console.log('stringTemplate tokens', tokens)
        return tokens;
    }

    /**
     * 生成多级成员表达式ast
     * @param {string} string - 传入对象属性字符串
     * @returns 对象属性字符串ast
     * 示例：
     * 'this.props.children'
     * => 
     * {
        type: 'MemberExpression',
        object: {
            type: 'MemberExpression',
            object: { type: 'Identifier', name: 'this' },
            property: { type: 'Identifier', name: 'props' },
            computed: false,
            optional: null
        },
        property: { type: 'Identifier', name: 'children' },
        computed: false,
        optional: null
        }
     */
    generateObjectAst = (string) => {
        const propertyArray = string.split('.');
        return propertyArray.reduce((previousValue, currentValue) => {
            return t.memberExpression(typeof previousValue === 'string' ? t.identifier(previousValue) : previousValue, t.identifier(currentValue))
        })
    }


    /**
     * 根据字符串获取对应的ast
     * @param {string} stringTemplate - 可能包含模版语法的字符串
     * @param {string} isStringChildren - 如果children是字符串，生成的字符串不能带引号
     * @returns ast
     */
     getStringTemplateAst = (stringTemplate, isStringChildren = false) => {
        const tokens = this.parseStringTemplate(stringTemplate);
        if (tokens.length <= 0) return '';
        
        let ast = []
        tokens.forEach(token => {
            if (token.type === 'text') {
                if (isStringChildren) {
                    ast.push(t.identifier(token.value));
                } else {
                    ast.push(t.stringLiteral(token.value));
                }
                
            } else if (token.type === '{') {
                const valueWithoutThis = token.value.replace(/^this./, '');
                const valueAst = this.generateObjectAst(valueWithoutThis);
                ast.push(t.jsxExpressionContainer(valueAst));
            }
        })
        return ast;
    }
}
